/*文件实用工具类：对文件进行操作
    1.获取文件大小
    2.获取文件最后一次修改时间
    3.获取文件最后一次访问时间
    4.获取文件路径名中的文件名称
    5.向文件中写入数据
    6.读取文件数据
    7.获取文件指定位置，指定长度的数据
    8.判断文件是否存在
    9.创建目录
    10.遍历文件夹里面所有的文件信息
    11.压缩文件
    12.解压文件
*/

#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <experimental/filesystem>
#include <jsoncpp/json/json.h>
#include <memory>

// #include "./logs/bin.hpp"

#include "bundle.h"

namespace cloud
{
    // bin::Logger::ptr logger =  bin::getLogger("cloud");
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
    private:
        std::string _filename;

    public:
        FileUtil(const std::string &filename)
            : _filename(filename)
        {
        }

        bool Remove()
        {
            if(!Exists())
                return false;
            remove(_filename.c_str());
            return true;
        }

        int64_t FileSize()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get file size failed\n";
                return -1;
            }
            return st.st_size;
        }
        time_t LastMTime() // 最后一次修改时间
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get file size failed\n";
                return -1;
            }
            return st.st_mtime;
        }
        time_t LastATime() // 最后一次访问时间
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get file size failed\n";
                return -1;
            }
            return st.st_atime;
        }

        std::string FileName()
        {
            // ./abc/test/txt
            size_t pos = _filename.find_last_of("/");
            if (pos == std::string::npos)
            {
                return _filename;
            }

            return _filename.substr(pos + 1);
        }

        bool GetPosLen(std::string *body, size_t pos, size_t len) // 获取文件指定位置数据
        {
            size_t fsize = FileSize();
            if (pos + len > fsize)
            {
                std::cout << "get file len is error" << std::endl;
                return false;
            }

            std::ifstream ifs;
            ifs.open(_filename, std::ios::binary);

            if (!ifs.is_open())
            {
                std::cout << "open file failed!" << std::endl;
                return false;
            }

            ifs.seekg(pos, std::ios::beg); // 相对于beg，偏移pos个数据
            body->resize(len);
            ifs.read(&((*body)[0]), len);
            if (!ifs.good())
            {
                std::cout << "get file content failed!" << std::endl;
                ifs.close();
                return false;
            }

            ifs.close();
            return true;
        }

        bool GetContent(std::string *body)
        {
            size_t fsize = FileSize();
            return GetPosLen(body, 0, fsize);
        }

        bool SetContent(const std::string &body) // 向文件中写入数据
        {
            std::ofstream ofs;
            ofs.open(_filename, std::ios::binary);

            if (!ofs.is_open())
            {
                std::cout << "open file failed!" << std::endl;
                return false;
            }

            ofs.write(&body[0], body.size());
            if (!ofs.good())
            {
                std::cout << "write file content failed!" << std::endl;
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }

        bool Compress(const std::string &packname) // 把文件中的数据先压缩，再写入新文件中
        {
            std::string body;
            if (!GetContent(&body))
            {
                std::cout << "compress get file content failed" << std::endl;
                return false;
            }

            std::string packed = bundle::pack(bundle::LZIP, body);

            FileUtil fu(packname);
            if (!fu.SetContent(packed))
            {
                std::cout << "compress write packed data failed" << std::endl;
            }

            return true;
        }

        bool UnCompress(const std::string &filename) // 把文件中的数据解压缩，并写入到新文件
        {
            std::string body;
            if (!GetContent(&body))
            {
                std::cout << "uncompress get file content failed" << std::endl;
                return false;
            }

            std::string unpacked = bundle::unpack(body);

            FileUtil fu(filename);
            if (!fu.SetContent(unpacked))
            {
                std::cout << "uncompress write unpacked data failed" << std::endl;
                return false;
            }

            return true;
        }

        bool Exists()
        {
            return fs::exists(_filename);
        }

        bool CreateDirectory()
        {
            if (Exists())
                return true;
            return fs::create_directories(_filename);
        }

        bool ScanDirectory(std::vector<std::string> *arry) // 遍历文件夹里面所有的文件信息
        {
            for (auto &p : fs::directory_iterator(_filename)) // directory_iterator(目标目录) :逐个获取目标目录下的文件
            {
                if (fs::is_directory(p)) // 如果是目录，就继续遍历下一个
                    continue;

                // 不是目录，才向数组里面添加属性信息
                // relative_path 带有相对路径的文件名
                arry->push_back(fs::path(p).relative_path().string());
            }
            return true;
        }

    };

    class JsonUtil
    {
    public:
        static bool Serialize(const Json::Value &root, std::string *str)
        {
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

            std::stringstream ss;
            sw->write(root, &ss);

            *str = ss.str();

            return true;
        }

        static bool Unserialize(const std::string &str, Json::Value *root)
        {
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

            std::string err;
            bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);

            if (ret == false)
            {
                std::cout << "parse error: " << err << std::endl;
                return -false;
            }
            return true;
        }
    };

}

